﻿namespace Home.Test.Sina
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Threading.Tasks;
    using System.Web;
    using Newtonsoft.Json;
    using Newtonsoft.Json.Linq;

    public class ConcreteIdentityProviderHandler : BaseIdentityProviderHandler
    {
        #region Request distribution

        protected override RequestType DetermineRequestType(HttpContext context)
        {
            var result = RequestType.Unknown;

            var segments = context.Request.Url.Segments;
            if (segments.Length == 2 && segments[0] == "sina")
            {
                switch (segments[1])
                {
                    case "authorize":
                        {
                            result = RequestType.Authorize;
                            break;
                        }
                    case "accesstoken":
                        {
                            result = RequestType.AccessToken;
                            break;
                        }
                    case "userinfo":
                        {
                            result = RequestType.UserInfo;
                            break;
                        }
                }
            }

            return result;
        }

        protected async override Task ProcessRequestAsync(HttpContext context, RequestType type)
        {
            switch (type)
            {
                case RequestType.Authorize:
                    {
                        await ProcessAuthorizeAsync(context);
                        break;
                    }
                case RequestType.AccessToken:
                    {
                        await ProcessAccessTokenAsync(context);
                        break;
                    }
                case RequestType.UserInfo:
                    {
                        await ProcessUserInfoAsync(context);
                        break;
                    }
            }
        }

        #endregion

        #region Process authorize

        private static async Task ProcessAuthorizeAsync(HttpContext context)
        {
            var request = context.Request;
            var response = context.Response;

            var queryString = new AuthorizationQueryString()
            {
                ClientId = request.QueryString["client_id"],
                ResponseType = request.QueryString["response_type"],
                RedirectUri = request.QueryString["redirect_uri"],
                Scope = request.QueryString["scope"],
                State = request.QueryString["state"],
                Display = request.QueryString["display"],
                Forcelogin = request.QueryString["forcelogin"],
                Language = request.QueryString["language"],
            };

            // validate request

            if (queryString.ResponseType != "code"
                || string.IsNullOrEmpty(queryString.RedirectUri)
                || string.IsNullOrEmpty(queryString.ClientId)
                || queryString.Scope != null
                || !(string.IsNullOrEmpty(queryString.Display) || queryString.Display == "default")
                || !(string.IsNullOrEmpty(queryString.Forcelogin) || queryString.Forcelogin == "true" || queryString.Forcelogin == "false")
                || queryString.Language != null)
            {
                await ResponseErrorAsync(context, Helper.IdentityProvider.CodeError);
                return;
            }

            var client = await GetClientAsync(queryString.ClientId);
            if (client == null || new Uri(queryString.RedirectUri, UriKind.Absolute) != new Uri(client.CallbackUrl, UriKind.Absolute))
            {
                await ResponseErrorAsync(context, Helper.IdentityProvider.CodeError);
                return;
            }

            // redirect user to login page if the user is not logged in

            var cookie = default(Cookie);
            if (queryString.Forcelogin == "true" || !Helper.Authentication.TryGetSignInData(context, IdentityProviderSource.Sina, out cookie))
            {
                var param = Helper.Crypto.Protect(queryString);
                var redirectUrl = Helper.Web.ToUrl("sina/login", new { param = param });
                redirectUrl = Helper.Web.ToAbsolute(context, redirectUrl);
                ResponseRedirect(context, redirectUrl);
                return;
            }

            // generate authorize code

            var consent = await GetConsentAsync(cookie.ConsentId);
            if (consent == null)
            {
                await ResponseErrorAsync(context, Helper.IdentityProvider.CodeError);
                return;
            }

            var code = Helper.Crypto.Protect(new Token()
            {
                Expires = DateTime.UtcNow.AddSeconds(Helper.Sina.DefaultExpiresDuration.AuthorizationCode),
                ConsentId = cookie.ConsentId,
            });

            // response

            var callback = Helper.Web.ToUrl(client.CallbackUrl, new { code = code, state = queryString.State });
            ResponseRedirect(context, callback);
        }

        #endregion

        #region Process access token

        private static async Task ProcessAccessTokenAsync(HttpContext context)
        {
            var request = context.Request;
            var response = context.Response;

            var queryString = new
            {
                ClientId = request.QueryString["client_id"],
                Secret = request.QueryString["client_secret"],
                Code = request.QueryString["code"],
                GrantType = request.QueryString["grant_type"],
                RedirectUri = request.QueryString["redirect_uri"],
            };

            // validate request

            if (queryString.GrantType != "authorization_code"
                || string.IsNullOrEmpty(queryString.ClientId)
                || string.IsNullOrEmpty(queryString.Secret)
                || string.IsNullOrEmpty(queryString.Code)
                || string.IsNullOrEmpty(queryString.RedirectUri))
            {
                await ResponseErrorAsync(context, Helper.IdentityProvider.CodeError);
                return;
            }

            var client = await GetClientAsync(queryString.ClientId);
            if (client == null || client.Secret != queryString.Secret || new Uri(queryString.RedirectUri, UriKind.Absolute) != new Uri(client.CallbackUrl, UriKind.Absolute))
            {
                await ResponseErrorAsync(context, Helper.IdentityProvider.CodeError);
                return;
            }

            var code = Helper.Crypto.Unprotect<Token>(queryString.Code);
            if (code == null || code.Expires <= DateTime.UtcNow)
            {
                await ResponseErrorAsync(context, Helper.IdentityProvider.CodeError);
                return;
            }

            // generate access token

            var accessToken = Helper.Crypto.Protect(new Token()
            {
                ConsentId = code.ConsentId,
                Expires = DateTime.UtcNow.AddSeconds(Helper.Sina.DefaultExpiresDuration.AccessToken),
            });

            // response

            var consent = await GetConsentAsync(code.ConsentId);
            if (consent == null)
            {
                await ResponseErrorAsync(context, Helper.IdentityProvider.CodeError);
                return;
            }

            var content = new JObject();
            content["access_token"] = accessToken;
            content["expires_in"] = Helper.Sina.DefaultExpiresDuration.AccessToken;
            content["remind_in"] = Helper.Sina.DefaultExpiresDuration.AccessToken;
            content["uid"] = Helper.ToCodeString(consent.SnsId);
            await ResponseJavaScriptAsync(context, content.ToString(Formatting.None));
        }

        #endregion

        #region Process user info

        private static async Task ProcessUserInfoAsync(HttpContext context)
        {
            var request = context.Request;
            var response = context.Response;

            var queryString = new
            {
                AccessToken = request.QueryString["access_token"],
                Uid = request.QueryString["uid"],
            };

            // validate request

            var uid = default(Guid);
            if (string.IsNullOrEmpty(queryString.AccessToken) || string.IsNullOrEmpty(queryString.Uid) || !Guid.TryParseExact(queryString.Uid, Helper.GuidFormat, out uid))
            {
                await ResponseErrorAsync(context, Helper.IdentityProvider.CodeError);
                return;
            }

            var accessToken = Helper.Crypto.Unprotect<Token>(queryString.AccessToken);
            if (accessToken == null || accessToken.Expires <= DateTime.UtcNow)
            {
                await ResponseErrorAsync(context, Helper.IdentityProvider.CodeError);
                return;
            }

            var consent = await GetConsentAsync(accessToken.ConsentId);
            if (consent == null || consent.SnsId != uid)
            {
                await ResponseErrorAsync(context, Helper.IdentityProvider.CodeError);
                return;

            }

            // response

            var sns = await GetSnsAsync(consent.SnsId);
            if (sns == null)
            {
                await ResponseErrorAsync(context, Helper.IdentityProvider.CodeError);
                return;
            }

            var content = new JObject();
            content["uid"] = queryString.Uid;
            content["nickname"] = sns.Nickname;
            await ResponseJsonAsync(context, content.ToString(Formatting.None));
        }

        #endregion

        #region others

        private static async Task ResponseErrorAsync(HttpContext context, int error)
        {
            var response = context.Response;
            response.Clear();
            response.StatusCode = (int)HttpStatusCode.OK;
            response.ContentType = Helper.ContentType.Json;
            var message = new JObject();
            message["error"] = Helper.IdentityProvider.Errors[error];
            message["error_code"] = error;
            message["error_description"] = Helper.IdentityProvider.Errors[error];
            await response.Output.WriteLineAsync(message.ToString(Formatting.None));
            await response.Output.FlushAsync();
        }

        #endregion
    }
}